#pragma once

#include <QWheelEvent>
#include <QPaintEvent>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QTimer>
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QDragEnterEvent>
#include <QRectF>
#include <QMarginsF>
#include <QHash>

#include <crs.h>
#include <pointxy.h>
#include <mapcanvasmap.h>
#include <maptool.h>

namespace EasyGIS {
class MapLayer;
class MapTool;
/**
 * 地图容器类，继承自QGraphicsView。
 * 整个地图的渲染都是基于“Qt GraphicsView Framework”设计。
 */
class MapCanvas : public QGraphicsView {
 Q_OBJECT

 signals:
  void zoomChanged(int zoom);
  void crsChanged();
  void clicked(PointXY pos);
  void mapCenterChanged(PointXY pos);

  friend class MapToolPan;
  friend class MapToolZoomIn;
  friend class MapToolZoomOut;
  friend class MapToolSelect;

 public:
  explicit MapCanvas(QWidget *parent = nullptr);
  ~MapCanvas() override;

 public:
  /**
   * 刷新地图容器，这会更新每一个显示的图层的内容
   */
  void refreshMap();

  /**
   * 向容器中添加图层，默认情况下容器会使用首个图层的坐标系作为自身的坐标系
   * @param layer 图层对象
   */
  void addLayer(MapLayer *layer);

  /**
   * 获取地图容器的比例尺
   * @return 容器的比例尺
   */
  double scale() const;

  /**
   * 获取地图容器的解析度
   * @return
   */
  double resolution() const;

  /**
   * 获取容器的zoom值
   * @return 容器的zoom值
   */
  int zoomValue() const;

  /**
   * 设置容器的zoom值
   * @param zoom zoom值
   */
  void setZoomValue(int zoom);

  /**
   * 获取容器当前所显示的地图区域
   * @return 当前所显示地图区域
   */
  const QRectF &viewExtent() const { return mViewExtent; }

  /**
   * 设置容器坐标系，设置过后会更新所有图层坐标系。
   * 默认会对容器中的图层做投影变换处理
   * @param crs 坐标系
   */
  void setCrs(const CRS *crs);

  /**
   * 获取地图容器的坐标系
   * @return 地图容器坐标系
   */
  const CRS &crs() const { return *mCrs; }

  /**
   * 设置地图容器的当前选中图层
   * @param id 图层的id
   */
  void setCurrentLayer(const QString &id);

  /**
   * 获取地图容器当前选中图层
   * @return 当前选中图层
   */
  const MapLayer *currentLayer() { return mCurrentLayer; }

  /**
   * 获取地图当前视图中心点经纬度坐标
   * @return 视图中心点经纬度坐标
   */
  const PointXY &mapCenter() const { return mMapCenter; }

  /**
   * 选择地图工具
   * @param tool 工具名称
   * @return 是否选择成功
   */
  bool selectTool(const QString &tool);

 protected:
  /**
   * 更新容器的可视化区域。
   * 每次拖动操作和resize事件会调用该方法
   */
  void updateViewExtent(bool reset = false);

  /**
   * 将屏幕坐标转换为经纬度坐标
   * @param point 屏幕上点
   * @return 经纬度坐标
   */
  PointXY pixel2Lonlat(const QPointF &point) const;

  /**
   * 初始设置地图必须具备的工具
   */
  void setupTools();

 private:
  /**
   * 确保zoom值在限定的范围之内
   * @param zoom 待设置zoom值
   * @return 可使用zoom值
   */
  int normalizeZoom(int zoom) const;

 protected:
  void mousePressEvent(QMouseEvent *event) override;
  void mouseReleaseEvent(QMouseEvent *event) override;
  void wheelEvent(QWheelEvent *event) override;
  void resizeEvent(QResizeEvent *event) override;

 protected:
  QGraphicsScene *mScene;
  QRectF mMapExtent;
  QRectF mViewExtent;
  QRectF mDragRect;
  bool mIsDragging;
  QMap<QString, MapLayer *> mLayers;
  MapLayer *mCurrentLayer;
  const CRS *mCrs;
  PointXY mMapCenter;
  int mZoomValue;
  PointXY mLastXY;
  QTimer *mMapUpdateTimer;
  MapTool *mCurrentTool;
  QHash<QString, MapTool *> mMapTools;

 private:
  const static int kDefaultZoomValue{8};
  static const int kMaxZoomValue{20};
  static const int kMinZoomValue{1};

 protected:
  static PointXY defaultMapCenter();
};

}